{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "GvJbBW_oDOwC"
   },
   "source": [
    "# Week 4: Handling Complex Images - Happy or Sad Dataset\n",
    "\n",
    "In this assignment you will be using the happy or sad dataset, which contains 80 images of emoji-like faces, 40 happy and 40 sad.\n",
    "\n",
    "Create a convolutional neural network that trains to 99.9% accuracy on these images,  which cancels training upon hitting this training accuracy threshold."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#### TIPS FOR SUCCESSFUL GRADING OF YOUR ASSIGNMENT:\n",
    "\n",
    "- All cells are frozen except for the ones where you need to submit your solutions or when explicitly mentioned you can interact with it.\n",
    "\n",
    "- You can add new cells to experiment but these will be omitted by the grader, so don't rely on newly created cells to host your solution code, use the provided places for this.\n",
    "\n",
    "- You can add the comment # grade-up-to-here in any graded cell to signal the grader that it must only evaluate up to that point. This is helpful if you want to check if you are on the right track even if you are not done with the whole assignment. Be sure to remember to delete the comment afterwards!\n",
    "\n",
    "- Avoid using global variables unless you absolutely have to. The grader tests your code in an isolated environment without running all cells from the top. As a result, global variables may be unavailable when scoring your submission. Global variables that are meant to be used will be defined in UPPERCASE.\n",
    "\n",
    "- To submit your notebook, save it and then click on the blue submit button at the beginning of the page."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/"
    },
    "deletable": false,
    "editable": false,
    "id": "3NFuMFYXtwsT",
    "outputId": "723d6bc3-c7cd-491b-d6f8-49a2e404a0a2",
    "tags": [
     "graded"
    ]
   },
   "outputs": [],
   "source": [
    "import os\n",
    "import base64\n",
    "import numpy as np\n",
    "import tensorflow as tf\n",
    "import matplotlib.pyplot as plt"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "deletable": false,
    "editable": false,
    "tags": []
   },
   "outputs": [],
   "source": [
    "import unittests"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Load and explore the data\n",
    "\n",
    "Begin by taking a look at some images of the dataset.\n",
    "\n",
    "All the images are contained within the `./data/` directory, notice that in this context the dot (`.`) means \"the current directory\". \n",
    "\n",
    "This `data/` directory contains two subdirectories `happy/` and `sad/` and each image is saved under the subdirectory related to the class it belongs to, take a look at the following tree for a more detailed view:\n",
    "\n",
    "```\n",
    ".\n",
    "└── data/\n",
    "    ├── happy/\n",
    "    │   ├── happy_image_1.png\n",
    "    │   ├── happy_image_2.png\n",
    "    │   └── ...\n",
    "    └── sad/\n",
    "        ├── sad_image_1.png\n",
    "        ├── sad_image_2.png\n",
    "        └── ...\n",
    "```"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/",
     "height": 369
    },
    "deletable": false,
    "editable": false,
    "id": "uaWTfp5Ox9E-",
    "outputId": "1a4b4b15-9a5f-4fd3-8c56-b32d47ae0893",
    "tags": [
     "graded"
    ]
   },
   "outputs": [],
   "source": [
    "BASE_DIR = \"./data/\"\n",
    "happy_dir = os.path.join(BASE_DIR, \"happy/\")\n",
    "sad_dir = os.path.join(BASE_DIR, \"sad/\")\n",
    "\n",
    "fig, axs = plt.subplots(1, 2, figsize=(6, 6))\n",
    "axs[0].imshow(tf.keras.utils.load_img(f\"{os.path.join(happy_dir, os.listdir(happy_dir)[0])}\"))\n",
    "axs[0].set_title('Example happy Face')\n",
    "\n",
    "axs[1].imshow(tf.keras.utils.load_img(f\"{os.path.join(sad_dir, os.listdir(sad_dir)[0])}\"))\n",
    "axs[1].set_title('Example sad Face')\n",
    "\n",
    "plt.tight_layout()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "It is cool to be able to see examples of the images to better understand the problem-space you are dealing with. \n",
    "\n",
    "However there is still some relevant information that is missing such as the resolution of the image (although matplotlib renders the images in a grid providing a good idea of these values) and the maximum pixel value (this is important for normalizing these values). For this you can use some `tf.keras` utility functions as shown in the next cell:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "deletable": false,
    "editable": false,
    "tags": [
     "graded"
    ]
   },
   "outputs": [],
   "source": [
    "# Load the first example of a happy face\n",
    "sample_image  = tf.keras.utils.load_img(f\"{os.path.join(happy_dir, os.listdir(happy_dir)[0])}\")\n",
    "\n",
    "# Convert the image into its numpy array representation\n",
    "sample_array = tf.keras.utils.img_to_array(sample_image)\n",
    "\n",
    "print(f\"Each image has shape: {sample_array.shape}\")\n",
    "\n",
    "print(f\"The maximum pixel value used is: {np.max(sample_array)}\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Looks like the images have a resolution of 150x150. **This is very important because this will be the input size of the first layer in your network.** \n",
    "\n",
    "**The last dimension refers to each one of the 3 RGB (Red, Green, Blue) channels that are used to represent colored images.** So far, in the previous assignments you used black and white images so it is time to introduce some color!"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Defining the callback\n",
    "\n",
    "Since you already have coded the callback responsible for stopping training (once a desired level of accuracy is reached) in the previous two assignments this time it is already provided so you can focus on the other steps:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "deletable": false,
    "editable": false,
    "id": "X0UOFLauzIW4",
    "tags": [
     "graded"
    ]
   },
   "outputs": [],
   "source": [
    "class EarlyStoppingCallback(tf.keras.callbacks.Callback):\n",
    "    def on_epoch_end(self, epoch, logs=None):\n",
    "        if logs['accuracy'] >= 0.999:\n",
    "            self.model.stop_training = True\n",
    "            print(\"\\nReached 99.9% accuracy so cancelling training!\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "So far you have implemented an `EarlyStoppingCallback` by customizing the `on_epoch_end` method but there is a version of this callback already available within `tf.keras`. You might want to check out the [EarlyStopping](https://www.tensorflow.org/api_docs/python/tf/keras/callbacks/EarlyStopping) callback, which has some extra functionality such as allowing you to save the best weights for your model and while at it take a look at all the other cool callbacks in the [docs](https://www.tensorflow.org/api_docs/python/tf/keras/callbacks)."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Exercise 1: training_dataset\n",
    "\n",
    "Up until now, in the previous 3 assignments you have used numpy arrays to hold your training data, which is a valid input for Tensorflow models. However it is often a better practice to use `tf.data.Dataset` since this provides extra functionality. You can even create these out of numpy arrays and many other data sources. Be sure to check the [docs](https://www.tensorflow.org/api_docs/python/tf/data/Dataset) to learn more about this, as you will use this extensively in the next courses of the specialization.\n",
    "\n",
    "You have covered some ground already and it is now time for your first task! \n",
    "\n",
    "You will now use the images of happy and sad faces to create your training dataset. Previously you used some `tf.keras` utility functions to work with image data. Now you will use one of the most powerful ones which is `image_dataset_from_directory`. Be sure to check out the [docs](https://www.tensorflow.org/api_docs/python/tf/keras/utils/image_dataset_from_directory) to see how this function is used and how its behaviour can be tweaked by providing different arguments for it. Remember to scale the images using a [Rescaling](https://www.tensorflow.org/api_docs/python/tf/keras/layers/Rescaling) layer and to apply this to the dataset by using the [map](https://www.tensorflow.org/api_docs/python/tf/data/Dataset#map) method as you saw in the ungraded labs! \n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "cellView": "code",
    "deletable": false,
    "id": "rrGO8ObGzqht",
    "tags": [
     "graded"
    ]
   },
   "outputs": [],
   "source": [
    "# GRADED FUNCTION: training_dataset\n",
    "\n",
    "def training_dataset():\n",
    "    \"\"\"Creates the training dataset out of the training images. Pixel values should be normalized.\n",
    "\n",
    "    Returns:\n",
    "        tf.data.Dataset: The dataset including the images of happy and sad faces.\n",
    "    \"\"\"\n",
    "    \n",
    "    ### START CODE HERE ###\n",
    "\n",
    "    # Specify the function to load images from a directory and pass in the appropriate arguments:\n",
    "    # - directory: should be a relative path to the directory containing the data. \n",
    "    #              You may hardcode this or use the previously defined global variable.\n",
    "    # - image_size: set this equal to the resolution of each image (excluding the color dimension)\n",
    "    # - batch_size: number of images the generator yields when asked for a next batch. Set this to 10.\n",
    "    # - class_mode: How the labels are represented. Should be one of \"binary\", \"categorical\" or \"int\".\n",
    "    #               Pick the one that better suits here given that the labels can only be two different values.\n",
    "    train_dataset = None(\n",
    "        directory=None,\n",
    "        image_size=None,\n",
    "        batch_size=None,\n",
    "        label_mode=None\n",
    "    )\n",
    "\n",
    "    # Define the rescaling layer (passing in the appropriate parameters)\n",
    "    rescale_layer = None\n",
    "\n",
    "    # Apply the rescaling by using the map method and a lambda\n",
    "    train_dataset_scaled = None\n",
    "    \n",
    "    ### END CODE HERE ###\n",
    "\n",
    "    return train_dataset_scaled\n",
    "    "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/"
    },
    "deletable": false,
    "editable": false,
    "id": "L9uxJFQb1nOx",
    "outputId": "0c6ce535-7764-4bc0-a4a4-e6289a360b04",
    "tags": [
     "graded"
    ]
   },
   "outputs": [],
   "source": [
    "# Save your generator in a variable\n",
    "train_data = training_dataset()\n",
    "\n",
    "for images, labels in train_data.take(1):\n",
    "    print(f\"Range for pixel values: {np.min(images[0]), np.max(images[0])}\")\n",
    "\n",
    "print(f\"train_data is an instance of tf.data.Dataset: {isinstance(train_data, tf.data.Dataset)}\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "**Expected Output:**\n",
    "```\n",
    "Found 80 files belonging to 2 classes.\n",
    "Range for pixel values: (0.0, 1.0)\n",
    "train_data is an instance of tf.data.Dataset: True\n",
    "```"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "deletable": false,
    "editable": false,
    "tags": []
   },
   "outputs": [],
   "source": [
    "# Test your code!\n",
    "unittests.test_train_data(train_data)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Exercise 2: create_and_compile_model\n",
    "\n",
    "Now that you have the training data ready it is time to define the model you will use to classify the happy and sad faces.\n",
    "\n",
    "**Your model should achieve an accuracy of 99.9% or more before 15 epochs to pass this assignment.**\n",
    "\n",
    "**Hints:**\n",
    "- The [Input](https://www.tensorflow.org/api_docs/python/tf/keras/Input) of your model should account for the shape of the data, which in this case is the size of each image plus the color dimension.\n",
    "\n",
    "\n",
    "\n",
    "- The last layer of your network should take into account the number of classes you are trying to predict and be compatible with the `label_mode` you defined in the previous exercise.\n",
    "\n",
    "- The selection of the loss function should take into consideration the `label_mode` you defined in the previous exercise and the last layer of your network. For a list of available loss functions click [here](https://www.tensorflow.org/api_docs/python/tf/keras/losses).\n",
    "\n",
    "- Remember to set the `accuracy` metric as the callback expects it.\n",
    "\n",
    "- You can try any architecture for the network but keep in mind that the model will work best with 3 convolutional layers. \n",
    "\n",
    "\n",
    "- In case you need extra help you can check out some tips at the end of this notebook."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "deletable": false,
    "id": "eUcNTpra1FK0",
    "tags": [
     "graded"
    ]
   },
   "outputs": [],
   "source": [
    "# GRADED FUNCTION: create_and_compile_model\n",
    "\n",
    "def create_and_compile_model():\n",
    "    \"\"\"Creates, compiles and trains the model to predict happy from sad faces.\n",
    "\n",
    "    Returns:\n",
    "        tf.keras.Model: The model that will be trained to predict predict happy and sad faces.\n",
    "    \"\"\"\n",
    "\n",
    "    ### START CODE HERE ###\n",
    "\n",
    "    # Define the model\n",
    "    model = tf.keras.models.Sequential([ \n",
    "\t\tNone\n",
    "    ]) \n",
    "\n",
    "    # Compile the model\n",
    "    # Remember to set an appropriate loss function, optimizer and metrics \n",
    "    None.None(\n",
    "        loss=None,\n",
    "        optimizer=None,\n",
    "        metrics=None\n",
    "    ) \n",
    "    \n",
    "    ### END CODE HERE ###\n",
    "\n",
    "    return model"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "The next cell allows you to check the number of total and trainable parameters of your model and prompts a warning in case these exceeds those of a reference solution, this serves the following 3 purposes listed in order of priority:\n",
    "\n",
    "- Helps you prevent crashing the kernel during training.\n",
    "\n",
    "- Helps you avoid longer-than-necessary training times.\n",
    "\n",
    "- Provides a reasonable estimate of the size of your model. In general you will usually prefer smaller models given that they accomplish their goal successfully.\n",
    "\n",
    "**Notice that this is just informative** and may be very well below the actual limit for size of the model necessary to crash the kernel. So even if you exceed this reference you are probably fine. However, **if the kernel crashes during training or it is taking a very long time and your model is larger than the reference, come back here and try to get the number of parameters closer to the reference.**"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "deletable": false,
    "editable": false,
    "tags": []
   },
   "outputs": [],
   "source": [
    "# Save untrained model in a variable\n",
    "model = create_and_compile_model()\n",
    "\n",
    "# Check parameter count against a reference solution\n",
    "unittests.parameter_count(model)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Check that the architecture you used is compatible with the dataset:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "deletable": false,
    "editable": false,
    "tags": []
   },
   "outputs": [],
   "source": [
    "# Get the first batch of images and labels\n",
    "for images, labels in train_data.take(1):\n",
    "\texample_batch_images = images\n",
    "\texample_batch_labels = labels\n",
    "\n",
    "try:\n",
    "\tmodel.evaluate(example_batch_images, example_batch_labels, verbose=False)\n",
    "except:\n",
    "\tprint(\"Your model is not compatible with the dataset you defined earlier. Check that the loss function, last layer and label_mode are compatible with one another.\")\n",
    "else:\n",
    "\tpredictions = model.predict(example_batch_images, verbose=False)\n",
    "\tprint(f\"predictions have shape: {predictions.shape}\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "**Expected Output:**\n",
    "```\n",
    "predictions have shape: (batch_size, n_units)\n",
    "```\n",
    "\n",
    "Where `batch_size` is the one you defined in the previous exercise (should be 10) and `n_units` is the number of units of the last layer of your model."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "deletable": false,
    "editable": false,
    "tags": []
   },
   "outputs": [],
   "source": [
    "# Test your code!\n",
    "unittests.test_create_and_compile_model(create_and_compile_model)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Notice that when using the `fit` method to train the model, you can pass in the whole `train_data` without explicitly separating features from labels. This is because `train_data` is a `tf.data.Dataset` and this operation is supported for objects of this class. For more info click [here](https://www.tensorflow.org/api_docs/python/tf/keras/Model#fit)."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "deletable": false,
    "editable": false
   },
   "outputs": [],
   "source": [
    "# Get the training history from your model\n",
    "training_history = model.fit(\n",
    "\tx=train_data,\n",
    "    epochs=15,\n",
    "    callbacks=[EarlyStoppingCallback()]\n",
    ")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "**Expected Output:**\n",
    "\n",
    "`Reached 99.9% accuracy so cancelling training!` printed out before reaching 15 epochs."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "deletable": false,
    "editable": false,
    "tags": []
   },
   "outputs": [],
   "source": [
    "# Test your code!\n",
    "unittests.test_training_history(training_history)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Need more help?\n",
    "\n",
    "Run the following cell to see some extra tips for the model's architecture and compilation parameters:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "deletable": false,
    "editable": false,
    "tags": []
   },
   "outputs": [],
   "source": [
    "encoded_answer = \"ClNvbWUgaGVscGZ1bCB0aXBzIGluIGNhc2UgeW91IGFyZSBzdHVjazoKCiAgICAtIFRoZSBpbnB1dCBzaG91bGQgYmUgYSB0Zi5rZXJhcy5JbnB1dCB3aXRoIGEgc2hhcGUgdGhhdCBtYXRjaGVzIAogICAgdGhhdCBvZiBldmVyeSBpbWFnZSBpbiB0aGUgdHJhaW5pbmcgc2V0IChpbmNsdWRpbmcgdGhlIGNvbG9yIGRpbWVuc2lvbikKICAgIAogICAgLSBBIGdvb2QgbGF5ZXIgKGFmdGVyIHRoZSBJbnB1dCkgd291bGQgYmUgYSBDb252MkQgbGF5ZXIKICAgIAogICAgLSBUaGUgbW9kZWwgd2lsbCB3b3JrIGJlc3Qgd2l0aCAzIGNvbnZvbHV0aW9uYWwgbGF5ZXJzCiAgICAKICAgIC0gVGhlcmUgc2hvdWxkIGJlIGEgRmxhdHRlbiBsYXllciBpbiBiZXR3ZWVuIGNvbnZvbHV0aW9uYWwgYW5kIGRlbnNlIGxheWVycwogICAgCiAgICAtIFRoZSBmaW5hbCBsYXllciBzaG91bGQgYmUgYSBEZW5zZSBsYXllciB3aXRoIHRoZSBudW1iZXIgb2YgdW5pdHMgYW5kIAogICAgYWN0aXZhdGlvbiBmdW5jdGlvbiB0aGF0IHN1cHBvcnRzIGJpbmFyeSBjbGFzc2lmaWNhdGlvbi4KCiAgICAtIEFkYW0gaXMgYSBnb29kIG9wdGltaXplciBpbiB0aGlzIGNhc2UuCgogICAgLSBBYm91dCBsb3NzIGZ1bmN0aW9uczoKCiAgICAgICAgLSBTcGFyc2VDYXRlZ29yaWNhbENyb3NzZW50cm9weSB3aWxsIHJlcXVpcmUgbGFiZWxfbW9kZSB0byBiZSAnaW50JyBvciAnYmluYXJ5JyAKICAgICAgICBhbmQgdGhlIGxhc3QgbGF5ZXIgc2hvdWxkIGhhdmUgdHdvIHVuaXRzIHdpdGggYSAnc29mdG1heCcgYWN0aXZhdGlvbiBmdW5jdGlvbi4KCiAgICAgICAgLSBCaW5hcnlDcm9zc2VudHJvcHkgd2lsbCByZXF1aXJlIGxhYmVsX21vZGUgdG8gYmUgJ2ludCcgb3IgJ2JpbmFyeScgCiAgICAgICAgYW5kIHRoZSBsYXN0IGxheWVyIHNob3VsZCBoYXZlIG9ubHkgb25lIHVuaXQgd2l0aCBhbiBhY3RpdmF0aW9uIGZ1bmN0aW9uIHN1Y2ggYXMgJ3NpZ21vaWQnLgoKICAgICAgICAtIENhdGVnb3JpY2FsQ3Jvc3NlbnRyb3B5IHdpbGwgcmVxdWlyZSBsYWJlbF9tb2RlIHRvIGJlICdjYXRlZ29yaWNhbCcKICAgICAgICBhbmQgdGhlIGxhc3QgbGF5ZXIgc2hvdWxkIGhhdmUgdHdvIHVuaXRzIHdpdGggYSAnc29mdG1heCcgYWN0aXZhdGlvbiBmdW5jdGlvbi4K==\"\n",
    "encoded_answer = encoded_answer.encode('ascii')\n",
    "answer = base64.b64decode(encoded_answer)\n",
    "answer = answer.decode('ascii')\n",
    "\n",
    "print(answer)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "**Congratulations on finishing the last assignment of this course!**\n",
    "\n",
    "You have successfully implemented a CNN to assist you in the classification task for complex images. Nice job!\n",
    "\n",
    "**Keep it up!**"
   ]
  }
 ],
 "metadata": {
  "grader_version": "1",
  "kernelspec": {
   "display_name": "Python 3 (ipykernel)",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.9.13"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 4
}
